package com.niodata.dp.web.module.task;

import com.google.common.base.Strings;
import com.niodata.dp.app.AppInfo;
import com.niodata.dp.app.AppManager;
import com.niodata.dp.core.OwnerType;
import com.niodata.dp.core.exception.InternalException;
import com.niodata.dp.core.exception.InvalidParamException;
import com.niodata.dp.core.exception.NotFoundException;
import com.niodata.dp.core.exception.PermissionDeniedException;
import com.niodata.dp.core.exception.ResourceNotFoundException;
import com.niodata.dp.core.resource.Resource;
import com.niodata.dp.core.resource.ResourceType;
import com.niodata.dp.core.resource.ResourceType.JobType;
import com.niodata.dp.core.resource.Resources;
import com.niodata.dp.core.security.AccessDeniedException;
import com.niodata.dp.core.service.SystemServices;
import com.niodata.dp.core.usermgr.model.GroupInfo;
import com.niodata.dp.core.usermgr.model.GroupProfile;
import com.niodata.dp.core.usermgr.model.UserGroup;
import com.niodata.dp.core.usermgr.model.UserInfo;
import com.niodata.dp.core.usermgr.op.GroupInfoService;
import com.niodata.dp.log.LogUtil;
import com.niodata.dp.monitor.sharelib.entity.task.TaskExecResult;
import com.niodata.dp.monitor.sharelib.service.TaskMonitorService;
import com.niodata.dp.task.TaskOptions;
import com.niodata.dp.task.dao.JobMapper;
import com.niodata.dp.task.entity.JobExecData;
import com.niodata.dp.task.entity.JobInfo;
import com.niodata.dp.task.entity.JobNodeStyleInfo;
import com.niodata.dp.task.entity.JobParams;
import com.niodata.dp.task.entity.PackageDefaultParams;
import com.niodata.dp.task.entity.PackageOutParam;
import com.niodata.dp.task.entity.TaskAlertSetting;
import com.niodata.dp.task.entity.TaskExecutionDetail;
import com.niodata.dp.task.entity.TaskExecutionDetail.JobExecDetailNode;
import com.niodata.dp.task.entity.TaskInfo;
import com.niodata.dp.task.entity.TaskParams;
import com.niodata.dp.task.entity.v2.PackageParamsV2;
import com.niodata.dp.task.entity.v2.PluginPackageV2;
import com.niodata.dp.task.service.PluginManagerV2;
import com.niodata.dp.task.service.impl.AzBasedTaskManagerV2;
import com.niodata.dp.util.IdUtil;
import com.niodata.dp.util.JsonUtil;
import com.niodata.dp.util.StringUtil;
import com.niodata.dp.web.PageInfo;
import com.niodata.dp.web.module.base.BaseController;
import com.niodata.dp.web.module.task.TaskGraph.NodeEdge;
import com.niodata.dp.web.module.task.TaskGraph.TaskNode;
import com.niodata.dp.web.security.ContextUtil;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.collections.map.HashedMap;
import org.apache.log4j.Logger;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.scheduling.support.CronExpression;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

@RestController("TaskController")
@RequestMapping("/v1/task")
public class TaskController extends BaseController {

  private static final Logger logger = Logger.getLogger(TaskController.class);

  @Qualifier("azTaskManager")
  @Autowired
  private AzBasedTaskManagerV2 taskManager;
  @Autowired
  @Qualifier("DpUserMgrGroup")
  private GroupInfoService groupInfoService;
  @Autowired
  @Qualifier("PluginManagerV2Impl")
  private PluginManagerV2 pluginManager;
  @Autowired
  private JobMapper jobMapper;
  @Autowired
  @Qualifier("appManagerImpl")
  private AppManager appManager;
  @Autowired
  @Qualifier("DpUserMgrGroup")
  private GroupInfoService groupOperation;

  @Autowired
  private TaskMonitorService taskMonitorService;


  /**
   * createTask.
   *
   * @param taskGraph TaskGraph
   * @return object
   */
  @RequestMapping(value = {"/admin/task"}, method = {RequestMethod.POST})
  public Object createTask(@RequestBody TaskGraph taskGraph) throws Exception {
    if (taskGraph.getDescription().trim().length() < 30) {
      throw new InvalidParamException("task desc length must be 30+");
    }
    Pattern pattern = Pattern.compile("[\\w+\\-]+");
    Matcher matcher = pattern.matcher(taskGraph.getTaskName());
    if (!matcher.matches()) {
      throw new InvalidParamException("task name can only contains a-zA-Z0-9-_");
    }
    this.checkNonNullCronString(taskGraph.getScheduleCron());
    taskGraph.setTaskId(IdUtil.uuid());
    TaskInfo taskInfo = this.buildTaskInfo(taskGraph);
    UserInfo userInfo = ContextUtil.getCurrentUser();
    this.operationAccessControl
          .checkServicePermission(userInfo.getUserId(), OwnerType.valueOf(taskInfo.getOwnerType()),
                taskInfo.getOwnerId(),
                SystemServices.getSystemService(ResourceType.Job.getTypeName()));
    taskInfo.setCreator(userInfo.getLoginName());
    taskInfo.setCreateTime(new Date());
    taskInfo.setModifyTime(taskInfo.getCreateTime());
    //check this group has dingAddr and emailAdrr
    GroupInfo groupInfo = null;
    try {
      groupInfo = groupInfoService.getGroupInfo(taskInfo.getOwnerId());
    } catch (Exception e) {
      LogUtil.logStackTrace(logger, e);
    }
    if (null != groupInfo) {
      List<GroupProfile> profileList = groupInfo.getProfiles();
      if (null != profileList && profileList.size() > 0) {
        if (taskInfo.getAlertSetting().isAlertEnabled()) {
          GroupProfile alertProfile = GroupInfo.getProfile(profileList, "alert");
          if (null != alertProfile) {
            if (taskInfo.getAlertSetting().isSendDefaultDing()) {
              if (StringUtil.isEmpty(alertProfile.getSetting("dingAddr"))) {
                throw new InvalidParamException("this group has no DING address!");
              }
            }

            if (taskInfo.getAlertSetting().isSendDefaultEmail()) {
              if (StringUtil.isEmpty(alertProfile.getSetting("emailAddr"))) {
                throw new InvalidParamException("this group has no email address!");
              }
            }
          }
        }
      }

    }
    this.taskManager.createTask(taskInfo);

    return getResult(true);
  }

  private void storeTaskRecovery(TaskInfo taskInfo) throws IOException {
    File dir = new File("task_recovery");
    dir.mkdirs();
    File recoveryFile = new File(dir.getAbsolutePath()
          + File.separator + taskInfo.getName());
    String json = JsonUtil.toJson(taskInfo);
    FileOutputStream out = new FileOutputStream(recoveryFile);
    try {
      out.write(json.getBytes());
    } finally {
      out.close();
    }
  }

  private void deleteTaskRecovery(String taskName) {
    File dir = new File("task_recovery");
    File recoveryFile = new File(dir.getAbsolutePath() + File.separator + taskName);
    recoveryFile.delete();
  }

  /**
   * createTask.
   */
  @RequestMapping(value = {"/admin/task/node/edit"},
        method = {RequestMethod.PUT, RequestMethod.POST})
  public Object editTaskNode(@RequestBody TaskGraph taskGraph) throws Exception {
    if (taskGraph.getDescription().trim().length() < 30) {
      throw new InvalidParamException("task description length must be 30+");
    }
    this.checkNonNullCronString(taskGraph.getScheduleCron());
    Pattern pattern = Pattern.compile("[\\w+\\-]+");
    Matcher matcher = pattern.matcher(taskGraph.getTaskName());
    if (!matcher.matches()) {
      throw new InvalidParamException("task name can only contains a-zA-Z0-9-_");
    }
    TaskInfo originTask = taskManager.getTaskDetailInfo(taskGraph.getTaskId());
    //save originTask
    storeTaskRecovery(originTask);
    logger.info("store origin task recovery " + taskGraph.getTaskName());
    TaskInfo taskInfo = null;
    try {
      taskInfo = this.buildTaskInfo(taskGraph);
    } catch (Exception e) {
      throw new ResourceNotFoundException("the plugin package is not exists,please resubmit task");
    }
    UserInfo userInfo = ContextUtil.getCurrentUser();
    this.operationAccessControl
          .checkServicePermission(userInfo.getUserId(), OwnerType.valueOf(taskInfo.getOwnerType()),
                taskInfo.getOwnerId(),
                SystemServices.getSystemService(ResourceType.Job.getTypeName()));
    TaskGraph originGraph = this.buildTaskGraph(originTask);
    //check originGraph node depend with now
    String originNode = originGraph.getEdges().stream().map(edge -> {
      return edge.getSource() + edge.getTarget();
    }).sorted().reduce((e1, e2) -> {
      return e1 + e2;
    }).orElseGet(() -> {
      return originGraph.getNodes().get(0).getJobName();
    });
    String newNode = taskGraph.getEdges().stream().map(edge -> {
      return edge.getSource() + edge.getTarget();
    }).sorted().reduce((e1, e2) -> {
      return e1 + e2;
    }).orElseGet(() -> {
      return taskGraph.getNodes().get(0).getJobName();
    });
    if (originNode.equals(newNode)) {
      if (originTask.isScheduled() || originTask.isDelProtection()) {
        deleteTaskRecovery(taskGraph.getTaskName());
      }
      //just update params
      updateTask(taskGraph);
      logger.info("just update params");
    } else {
      //resubmit task
      taskInfo.setCreateTime(originTask.getCreateTime());
      taskInfo.setModifyTime(new Date());
      taskManager.deleteTaskByName(taskGraph.getTaskName());
      taskInfo.setCreator(userInfo.getLoginName());
      this.taskManager.createTask(taskInfo);
      if (originTask.isScheduled()) {
        taskManager.scheduleTask(taskGraph.getTaskId());
      }
      logger.info("resubmit task");
    }
    deleteTaskRecovery(taskGraph.getTaskName());
    logger.info("delete origin task recovery " + taskInfo.getName());
    GroupInfo groupInfo = groupInfoService.getGroupInfo(taskInfo.getOwnerId());
    //.updateTaskAlertCategory(alertBaseUrl, groupInfo, taskInfo);
    return getResult(true);
  }


  @RequestMapping(value = {"/view/resubmit/task"}, method = {RequestMethod.GET})
  public Object reSubmitTask(@RequestParam("taskId") String taskId) throws Exception {
    this.taskManager.reSubmitTask(taskId);
    return getResult(true);
  }

  /**
   * copy task .
   *
   * @param taskCopyModel taskCopyModel
   * @return result
   * @throws Exception exception
   */
  @RequestMapping(value = {"/admin/task/copy"}, method = {RequestMethod.POST})
  public Object copyTask(@RequestBody TaskCopyModel taskCopyModel) throws Exception {
    Pattern pattern = Pattern.compile("[\\w+\\-]+");
    Matcher matcher = pattern.matcher(taskCopyModel.getTaskCopyName());
    if (!matcher.matches()) {
      throw new InvalidParamException("task name can only contains a-zA-Z0-9-_");
    }
    UserInfo userInfo = ContextUtil.getCurrentUser();
    TaskInfo taskInfo = this.taskManager.getTaskDetailInfo(taskCopyModel.getTaskId());
    TaskGraph taskGraph = buildCopyTaskGraph(taskInfo, taskCopyModel.getTaskCopyName());
    TaskInfo taskInfoCopy = buildCopyTaskInfo(taskGraph, taskCopyModel);
    taskInfoCopy.setCreator(userInfo.getUserName());
    //调创建告警设置接口
    TaskAlertSetting taskAlertSetting = taskInfo.getAlertSetting();
    //是否需要告警
    if (null != taskAlertSetting && taskAlertSetting.isAlertEnabled()) {
      //2.调用DpAlertClient sdk,将配置信息传给告警服务
      GroupInfo groupInfo = groupInfoService.getGroupInfo(taskInfoCopy.getOwnerId());
      //AlertUtil.createTaskAlertCategory(alertBaseUrl, groupInfo, taskInfoCopy);
    }
    taskInfoCopy.setCreateTime(new Date());
    taskInfoCopy.setModifyTime(taskInfoCopy.getCreateTime());
    this.taskManager.createTask(taskInfoCopy);
    Map<String, String> copyResult = new HashMap<>(2);
    copyResult.put("taskId", taskInfoCopy.getTaskId());
    copyResult.put("name", taskInfoCopy.getName());
    return getResult(copyResult);
  }

  /**
   * import task from json file.
   *
   * @param file dumpgraph json
   * @param taskName task name,optional
   * @param description description optional
   * @return success
   * @throws Exception ex
   */
  @RequestMapping(value = "/admin/task/import", method = RequestMethod.POST)
  public Object importTask(@RequestParam(value = "file") MultipartFile file,
        @RequestParam(value = "taskName", required = false) String taskName,
        @RequestParam(value = "description", required = false) String description,
        @RequestParam("ownerId") long ownerId) throws Exception {
    UserInfo userInfo = ContextUtil.getCurrentUser();
    if (!groupOperation.isMasterOrOwner(userInfo.getUserId(), ownerId)) {
      throw new PermissionDeniedException("无权创建任务");
    }
    byte[] bytes = file.getBytes();
    TaskDumpGraph dumpGraph =
          JsonUtil.fromJson(TaskDumpGraph.class, new String(bytes, StandardCharsets.UTF_8));
    List<TaskNode> taskNodes = dumpGraph.getNodes();
    Set<String> pkgNotFound = new HashSet();
    for (TaskNode node : taskNodes) {
      PluginPackageV2 pkg = this.pluginManager.getPlugin(node.getPkgName(), node.getPkgVersion());
      if (pkg == null) {
        pkgNotFound.add(node.getPkgName() + "-" + node.getPkgVersion());
      } else {
        node.setPkgId(pkg.getPkgId());
      }
    }
    if (pkgNotFound.size() > 0) {
      String msg = "以下插件不存在:\r\n" + String.join(",", pkgNotFound.toArray(new String[pkgNotFound.size()]));
      throw new InvalidParamException(msg);
    } else {
      TaskGraph taskGraph = new TaskGraph();
      taskGraph.setOwnerId(ownerId);
      taskGraph.setOwnerType(OwnerType.GROUP.name());
      BeanUtils.copyProperties(dumpGraph, taskGraph);
      if (!Strings.isNullOrEmpty(taskName)) {
        taskGraph.setTaskName(taskName);
      }
      if (!Strings.isNullOrEmpty(description)) {
        taskGraph.setDescription(description);
      }
      TaskInfo taskInfo = buildTaskInfo(taskGraph);
      taskInfo.setCreator(userInfo.getLoginName());
      taskInfo.setCreateTime(new Date());
      taskInfo.setModifyTime(taskInfo.getCreateTime());
      this.taskManager.createTask(taskInfo);
    }
    return getResult("ok");
  }

  /**
   * dump task definition.
   *
   * @param taskId task id
   * @param response http response
   * @throws Exception ex
   */
  @RequestMapping(value = {"/admin/task/dump"}, method = {RequestMethod.POST, RequestMethod.GET})
  public void dumpTask(@RequestParam(name = "id") String taskId, HttpServletResponse response)
        throws Exception {
    TaskInfo taskInfo = taskManager.getTaskDetailInfo(taskId);
    if (taskInfo == null) {
      throw new NotFoundException("task not found:" + taskId);
    }
    TaskGraph taskGraph = this.buildTaskGraph(taskInfo);
    TaskDumpGraph dumpGraph = TaskDumpGraph.buildFromTaskGraph(taskGraph);
    String json = JsonUtil.toJson(dumpGraph, true);
    response.setHeader("Content-Disposition", "attachment; filename="
          + taskInfo.getName() + ".json");
    response.setContentType("application/json");
    response.getWriter().write(json);
    response.flushBuffer();
  }

  private TaskGraph buildCopyTaskGraph(TaskInfo taskInfo, String copyName) {
    TaskGraph taskGraph = new TaskGraph();
    taskGraph.setAlertSetting(taskInfo.getAlertSetting());
    taskGraph.setAppId(taskInfo.getAppId());
    taskGraph.setAppName(taskInfo.getAppName());
    taskGraph.setDescription(taskInfo.getDescription());
    taskGraph.setOwnerId(taskInfo.getOwnerId());
    taskGraph.setOwnerType(taskInfo.getOwnerType());
    taskGraph.setScheduleCron(taskInfo.getScheduleCron());
    taskGraph.setTaskName(copyName);
    taskGraph.setTaskId(IdUtil.uuid());
    taskGraph.setBindExecutor(taskInfo.isBindExecutor());
    taskGraph.setExecutorId(taskInfo.getExecutorId());
    taskGraph.setConcurrentOption(
          taskInfo.getOptions() != null ? taskInfo.getOptions().getConcurrentOption() : "");
    Map<String, String> param = new HashMap<>();
    if (taskInfo.getParams() != null && taskInfo.getParams().getParams() != null) {
      Map<String, Object> taskParams = taskInfo.getParams().getParams();
      taskParams.forEach(new BiConsumer<String, Object>() {
        public void accept(String key, Object v) {
          if (v != null) {
            param.put(key, v.toString());
          }
        }
      });
    }

    taskGraph.setTaskParams(param);
    List<NodeEdge> edges = new ArrayList<>();
    Map<String, String> jobIdCopy = new HashMap<>();
    taskInfo.getJobInfoList().forEach(l -> {
      jobIdCopy.put(l.getJobId(), IdUtil.uuid());
    });
    taskInfo.getJobInfoList().forEach(new Consumer<JobInfo>() {
      @Override
      public void accept(JobInfo jobInfo) {
        if (jobInfo.getDepJobs() != null) {
          for (JobInfo jb : jobInfo.getDepJobs()) {
            NodeEdge edge = new NodeEdge();
            edge.setTarget(jobIdCopy.get(jobInfo.getJobId()));
            edge.setSource(jobIdCopy.get(jb.getJobId()));
            edges.add(edge);
          }
        }
      }
    });
    taskGraph.setEdges(edges);
    taskGraph.getEdges().forEach(l -> {
      System.out.println(l.getSource() + "------" + l.getTarget());
    });
    List<TaskNode> nodes =
          taskInfo.getJobInfoList().stream().map(new Function<JobInfo, TaskNode>() {
            @Override
            public TaskNode apply(JobInfo jobInfo) {
              TaskNode node = new TaskNode();
              node.setClassName(jobInfo.getNodeStyleInfo().getClassName());
              node.setStyle(jobInfo.getNodeStyleInfo().getStyleInfo());
              node.setJobName(jobInfo.getJobName());
              node.setNodeId(jobIdCopy.get(jobInfo.getJobId()));
              node.setParams(jobInfo.getParams().getParams());
              node.getParams().remove("keytab");
              node.getParams().remove("principal");
              node.setPkgName(jobInfo.getPkgName());
              node.setPkgId(jobInfo.getPkgId());
              return node;
            }
          }).collect(Collectors.toList());
    taskGraph.setNodes(nodes);
    return taskGraph;
  }

  private TaskInfo buildCopyTaskInfo(TaskGraph taskGraph, TaskCopyModel taskCopyModel) {
    TaskInfo taskInfo = new TaskInfo();
    taskInfo.setAlertSetting(taskGraph.getAlertSetting());
    if (taskGraph.getConcurrentOption() != null) {
      TaskOptions options = new TaskOptions();
      options.setConcurrentOption(taskGraph.getConcurrentOption());
      taskInfo.setOptions(options);
    }
    AppInfo appInfo = appManager.getByAppId(taskGraph.getAppId());
    if (appInfo != null) {
      taskInfo.setAppName(appInfo.getName());
    }
    taskInfo.setAppId(taskGraph.getAppId());
    TaskParams params = new TaskParams();
    Map<String, Object> paramMap = new HashedMap();
    if (taskGraph.getTaskParams() != null) {
      paramMap.putAll(taskGraph.getTaskParams());
    }
    params.setParams(paramMap);
    taskInfo.setParams(params);
    taskInfo.setTaskId(IdUtil.uuid());
    taskInfo.setBindExecutor(taskGraph.isBindExecutor());
    taskInfo.setExecutorId(taskGraph.getExecutorId());
    taskInfo.setName(taskGraph.getTaskName());
    if (OwnerType.PUBLIC.name().equals(taskGraph.getOwnerType())) {
      taskInfo.setOwnerType(OwnerType.GROUP.name());
      GroupInfo publicGroup = this.groupInfoService.getGroupInfo("All");
      taskInfo.setOwnerId(publicGroup.getGroupId());
    } else if (OwnerType.USER.name().equals(taskGraph.getOwnerType())) {
      taskInfo.setOwnerType(OwnerType.USER.name());
      UserInfo userInfo = ContextUtil.getCurrentUser();
      taskInfo.setOwnerId(userInfo.getUserId());
    } else if (OwnerType.GROUP.name().equals(taskGraph.getOwnerType())) {
      taskInfo.setOwnerType(OwnerType.GROUP.name());
      taskInfo.setOwnerId(taskGraph.getOwnerId());
    } else {
      throw new RuntimeException("param error");
    }
    taskInfo.setScheduleCron(taskGraph.getScheduleCron());
    taskInfo.setDescription(taskGraph.getDescription());
    taskInfo.setScheduled(false);
    Map<String, JobInfo> jobInfoMap = new HashMap<>();
    for (TaskNode taskNode : taskGraph.getNodes()) {
      JobInfo jobInfo = new JobInfo();
      jobInfo.setTaskId(taskInfo.getTaskId());
      jobInfo.setJobId(taskNode.getNodeId());
      jobInfo.setJobName(taskNode.getJobName());
      PluginPackageV2 pkgCopy = pluginManager
            .getPluginByNameVersion(ContextUtil.getCurrentUser().getUserId(), taskNode.getPkgName(),
                  taskCopyModel.getPlugins().get(taskNode.getPkgName()));
      jobInfo.setPkgName(pkgCopy.getPkgName());
      jobInfo.setPkgVersion(pkgCopy.getPkgVersion());
      jobInfo.setPkgId(pkgCopy.getPkgId());
      jobInfo.setPkgType(pkgCopy.getPkgType());
      jobInfo.setPkgLang(pkgCopy.getPkgLang());
      jobInfo.setJobType(pkgCopy.getJobType());
      JobParams jobParams = new JobParams();
      //set copy pkg default param
      PackageDefaultParams defaultParams = pkgCopy.getDefaultParams();
      if (defaultParams != null) {
        defaultParams.getParams().entrySet().forEach(new Consumer<Entry<String, String>>() {
          @Override
          public void accept(Entry<String, String> paramEntry) {
            jobParams.setParam(paramEntry.getKey(), paramEntry.getValue());
          }
        });
      }
      PackageParamsV2 pkgParams = pkgCopy.getParams();
      pkgParams.getGroups();
      Map<String, Object> originParams = taskNode.getParams();
      if (pkgParams != null && pkgParams.getGroups() != null && pkgParams.getGroups().size() > 0) {
        for (PackageParamsV2.PackageParamGroup group : pkgParams.getGroups()) {
          List<PackageParamsV2.PackageParamV2> relatedParams = group.getRelatedParams();
          if (relatedParams != null && relatedParams.size() > 0) {
            relatedParams.stream().forEach(new Consumer<PackageParamsV2.PackageParamV2>() {
              @Override
              public void accept(PackageParamsV2.PackageParamV2 copyPkgParam) {
                if (!copyPkgParam.isUserSetAble()) {
                  return;
                }
                if (originParams.get(copyPkgParam.getName()) != null) {
                  jobParams.setParam(copyPkgParam.getName(),
                        originParams.get(copyPkgParam.getName()).toString());
                } else if (copyPkgParam.getDefaultValue() != null) {
                  jobParams.setParam(copyPkgParam.getName(), copyPkgParam.getDefaultValue());
                } else {
                  jobParams.setParam(copyPkgParam.getName(), "");
                }
              }
            });
          }
        }
      }
      jobInfo.setParams(jobParams);
      jobInfo.setPkgPath(pkgCopy.getPkgPath());
      JobNodeStyleInfo styleInfo = new JobNodeStyleInfo();
      styleInfo.setClassName(taskNode.getClassName());
      styleInfo.setStyleInfo(taskNode.getStyle());
      jobInfo.setNodeStyleInfo(styleInfo);
      jobInfoMap.put(jobInfo.getJobId(), jobInfo);
    }
    for (NodeEdge nodeEdge : taskGraph.getEdges()) {
      System.out.println(nodeEdge.getSource() + "------" + nodeEdge.getTarget());
      JobInfo jobInfo = jobInfoMap.get(nodeEdge.getTarget());
      if (jobInfo.getDepJobs() == null) {
        jobInfo.setDepJobs(new ArrayList<JobInfo>(3));
      }
      JobInfo depJobInfo = jobInfoMap.get(nodeEdge.getSource());
      jobInfo.getDepJobs().add(depJobInfo);
    }
    List<JobInfo> jobInfoList = new ArrayList<>(jobInfoMap.size());
    jobInfoList.addAll(jobInfoMap.values());
    taskInfo.setJobInfoList(jobInfoList);
    return taskInfo;
  }

  private TaskInfo buildTaskInfo(TaskGraph taskGraph) {
    if (!ObjectUtils.isEmpty(taskGraph.getScheduleCron())
          && !CronExpression.isValidExpression(taskGraph.getScheduleCron())) {
      throw new InvalidParamException("Invalid cron expression");
    }
    TaskInfo taskInfo = new TaskInfo();
    taskInfo.setAlertSetting(taskGraph.getAlertSetting());
    if (taskGraph.getConcurrentOption() != null) {
      TaskOptions options = new TaskOptions();
      options.setConcurrentOption(taskGraph.getConcurrentOption());
      taskInfo.setOptions(options);
    }
    taskInfo.setDelProtection(taskGraph.isDelProtection());
    AppInfo appInfo = appManager.getByAppId(taskGraph.getAppId());
    if (appInfo != null) {
      taskInfo.setAppName(appInfo.getName());
    }
    taskInfo.setAppId(taskGraph.getAppId());
    TaskParams params = new TaskParams();
    Map<String, Object> paramMap = new HashedMap();
    if (taskGraph.getTaskParams() != null) {
      paramMap.putAll(taskGraph.getTaskParams());
    }
    params.setParams(paramMap);
    taskInfo.setParams(params);
    taskInfo.setTaskId(taskGraph.getTaskId());
    taskInfo.setBindExecutor(taskGraph.isBindExecutor());
    taskInfo.setExecutorId(taskGraph.getExecutorId());
    taskInfo.setName(taskGraph.getTaskName());
    if (OwnerType.PUBLIC.name().equals(taskGraph.getOwnerType())) {
      taskInfo.setOwnerType(OwnerType.GROUP.name());
      GroupInfo publicGroup = this.groupInfoService.getGroupInfo("All");
      taskInfo.setOwnerId(publicGroup.getGroupId());
    } else if (OwnerType.USER.name().equals(taskGraph.getOwnerType())) {
      taskInfo.setOwnerType(OwnerType.USER.name());
      UserInfo userInfo = ContextUtil.getCurrentUser();
      taskInfo.setOwnerId(userInfo.getUserId());
    } else if (OwnerType.GROUP.name().equals(taskGraph.getOwnerType())) {
      taskInfo.setOwnerType(OwnerType.GROUP.name());
      taskInfo.setOwnerId(taskGraph.getOwnerId());
    } else {
      throw new InvalidParamException("param error");
    }
    taskInfo.setScheduled(taskGraph.isScheduled());
    taskInfo.setScheduleCron(taskGraph.getScheduleCron());
    taskInfo.setDescription(taskGraph.getDescription());

    Map<String, JobInfo> jobInfoMap = new HashMap<>();
    for (TaskNode taskNode : taskGraph.getNodes()) {
      JobInfo jobInfo = new JobInfo();
      jobInfo.setTaskId(taskInfo.getTaskId());
      jobInfo.setJobId(taskNode.getNodeId());
      jobInfo.setJobName(taskNode.getJobName());
      PluginPackageV2 pkg = pluginManager.getPlugin(taskNode.getPkgId());
      JobInfo jobInfoInDb = this.jobMapper.getJobByName(taskNode.getJobName(), taskInfo.getName());
      if (pkg == null) {
        logger.error("pkg not found for id:" + taskNode.getPkgId());
        if (jobInfoInDb == null) {
          throw new ResourceNotFoundException("pkg and job info not found");
        }
        jobInfo.setPkgName(jobInfoInDb.getPkgName());
        jobInfo.setPkgVersion(jobInfoInDb.getPkgVersion());
        jobInfo.setPkgId(jobInfoInDb.getPkgId());
        jobInfo.setPkgType(jobInfoInDb.getPkgType());
        jobInfo.setPkgLang(jobInfoInDb.getPkgLang());
        jobInfo.setJobType(jobInfoInDb.getJobType());
        JobParams jobParams = new JobParams();
        jobParams.setParams(taskNode.getParams());
        jobInfo.setParams(jobParams);
        jobInfo.setPkgPath(jobInfoInDb.getPkgPath());
        JobNodeStyleInfo styleInfo = new JobNodeStyleInfo();
        styleInfo.setClassName(taskNode.getClassName());
        styleInfo.setStyleInfo(taskNode.getStyle());
        jobInfo.setNodeStyleInfo(styleInfo);
        jobInfoMap.put(jobInfo.getJobId(), jobInfo);
      } else {
        jobInfo.setPkgName(pkg.getPkgName());
        jobInfo.setPkgVersion(pkg.getPkgVersion());
        jobInfo.setPkgId(pkg.getPkgId());
        jobInfo.setPkgType(pkg.getPkgType());
        jobInfo.setPkgLang(pkg.getPkgLang());
        jobInfo.setJobType(pkg.getJobType());
        JobParams jobParams = new JobParams();
        jobParams.setParams(taskNode.getParams());
        jobInfo.setParams(jobParams);
        jobInfo.setPkgPath(pkg.getPkgPath());
        JobNodeStyleInfo styleInfo = new JobNodeStyleInfo();
        styleInfo.setClassName(taskNode.getClassName());
        styleInfo.setStyleInfo(taskNode.getStyle());
        jobInfo.setNodeStyleInfo(styleInfo);
        jobInfoMap.put(jobInfo.getJobId(), jobInfo);
      }

    }
    for (NodeEdge nodeEdge : taskGraph.getEdges()) {
      JobInfo jobInfo = jobInfoMap.get(nodeEdge.getTarget());
      if (jobInfo.getDepJobs() == null) {
        jobInfo.setDepJobs(new ArrayList<JobInfo>(3));
      }
      JobInfo depJobInfo = jobInfoMap.get(nodeEdge.getSource());
      jobInfo.getDepJobs().add(depJobInfo);
    }
    List<JobInfo> jobInfoList = new ArrayList<>(jobInfoMap.size());
    jobInfoList.addAll(jobInfoMap.values());
    taskInfo.setJobInfoList(jobInfoList);
    return taskInfo;
  }

  private TaskGraph buildTaskGraph(TaskInfo taskInfo) {
    TaskGraph taskGraph = new TaskGraph();
    taskGraph.setAlertSetting(taskInfo.getAlertSetting());
    taskGraph.setAppId(taskInfo.getAppId());
    taskGraph.setAppName(taskInfo.getAppName());
    taskGraph.setDescription(taskInfo.getDescription());
    taskGraph.setOwnerId(taskInfo.getOwnerId());
    taskGraph.setOwnerType(taskInfo.getOwnerType());
    taskGraph.setScheduleCron(taskInfo.getScheduleCron());
    taskGraph.setTaskName(taskInfo.getName());
    taskGraph.setTaskId(taskInfo.getTaskId());
    taskGraph.setBindExecutor(taskInfo.isBindExecutor());
    taskGraph.setExecutorId(taskInfo.getExecutorId());
    taskGraph.setDelProtection(taskInfo.isDelProtection());
    taskGraph.setScheduled(taskInfo.isScheduled());
    taskGraph.setScheduledTime(taskInfo.getScheduledTime());
    taskGraph.setConcurrentOption(
          taskInfo.getOptions() != null ? taskInfo.getOptions().getConcurrentOption() : "");
    Map<String, String> param = new HashMap<>();
    if (taskInfo.getParams() != null && taskInfo.getParams().getParams() != null) {
      Map<String, Object> taskParams = taskInfo.getParams().getParams();
      taskParams.forEach(new BiConsumer<String, Object>() {
        public void accept(String key, Object v) {
          if (v != null) {
            param.put(key, v.toString());
          }
        }
      });
    }

    taskGraph.setTaskParams(param);
    List<NodeEdge> edges = new ArrayList<>();
    taskGraph.setEdges(edges);
    taskInfo.getJobInfoList().forEach(new Consumer<JobInfo>() {
      @Override
      public void accept(JobInfo jobInfo) {
        if (jobInfo.getDepJobs() != null) {
          for (JobInfo jb : jobInfo.getDepJobs()) {
            NodeEdge edge = new NodeEdge();
            edge.setTarget(jobInfo.getJobId());
            edge.setSource(jb.getJobId());
            edges.add(edge);
          }
        }
      }
    });
    List<TaskNode> nodes =
          taskInfo.getJobInfoList().stream().map(new Function<JobInfo, TaskNode>() {
            @Override
            public TaskNode apply(JobInfo jobInfo) {
              TaskNode node = new TaskNode();
              node.setClassName(jobInfo.getNodeStyleInfo().getClassName());
              node.setStyle(jobInfo.getNodeStyleInfo().getStyleInfo());
              node.setJobName(jobInfo.getJobName());
              node.setNodeId(jobInfo.getJobId());
              JobParams jobParams = jobInfo.filterOutInternalParams();
              node.setParams(jobParams.getParams());
              node.setPkgId(jobInfo.getPkgId());
              node.setPkgName(jobInfo.getPkgName());
              node.setPkgVersion(jobInfo.getPkgVersion());
              return node;
            }
          }).collect(Collectors.toList());
    taskGraph.setNodes(nodes);
    return taskGraph;
  }


  private TaskExecutionGraph buildTaskExecutionGraph(int execId, TaskInfo taskInfo) {
    TaskExecutionGraph taskGraph = new TaskExecutionGraph();
    taskGraph.setDescription(taskInfo.getDescription());
    taskGraph.setOwnerId(taskInfo.getOwnerId());
    taskGraph.setOwnerType(taskInfo.getOwnerType());
    taskGraph.setScheduleCron(taskInfo.getScheduleCron());
    taskGraph.setTaskName(taskInfo.getName());
    taskGraph.setTaskId(taskInfo.getTaskId());
    taskGraph.setExecId(execId);
    if (taskInfo.getParams() != null && taskInfo.getParams().getParams() != null
          && taskInfo.getParams().getParams().size() > 0) {
      Map<String, String> param = new HashMap<>();
      Map<String, Object> taskParams = taskInfo.getParams().getParams();
      taskParams.forEach(new BiConsumer<String, Object>() {
        public void accept(String key, Object v) {
          if (v != null) {
            param.put(key, v.toString());
          }
        }
      });
      taskGraph.setTaskParams(param);
    }
    List<TaskExecutionGraph.NodeEdge> edges = new ArrayList<>();
    TaskExecutionDetail executionDetail = this.taskManager.getTaskExecutionDetail(execId);
    taskGraph.setStatus(executionDetail.getStatus());
    taskGraph.setStartTime(executionDetail.getStartTime());
    taskGraph.setEdges(edges);

    taskInfo.getJobInfoList().forEach(new Consumer<JobInfo>() {
      @Override
      public void accept(JobInfo jobInfo) {
        if (jobInfo.getDepJobs() != null) {
          for (JobInfo jb : jobInfo.getDepJobs()) {
            TaskExecutionGraph.NodeEdge edge = new TaskExecutionGraph.NodeEdge();
            edge.setSource(jb.getJobId());
            edge.setTarget(jobInfo.getJobId());
            edges.add(edge);
          }
        }
      }
    });
    List<JobExecData> execDataList =
          taskManager.getJobExecDataOfTask(taskInfo.getTaskId(), execId);
    List<TaskExecutionGraph.TaskNode> nodes =
          taskInfo.getJobInfoList().stream()
                .map(new Function<JobInfo, TaskExecutionGraph.TaskNode>() {
                  @Override
                  public TaskExecutionGraph.TaskNode apply(JobInfo jobInfo) {
                    TaskExecutionGraph.TaskNode node = new TaskExecutionGraph.TaskNode();
                    node.setClassName(jobInfo.getNodeStyleInfo().getClassName());
                    node.setStyle(jobInfo.getNodeStyleInfo().getStyleInfo());
                    node.setJobName(jobInfo.getJobName());
                    node.setNodeId(jobInfo.getJobId());
                    node.setParams(jobInfo.getParams().getParams());
                    node.setPkgId(jobInfo.getPkgId());
                    node.setJobType(jobInfo.getJobType());
                    for (JobExecDetailNode nodeDetail : executionDetail.getNodes()) {
                      if (nodeDetail.id.equals(jobInfo.getJobName())) {
                        node.setStatus(nodeDetail.status);
                        node.setStartTime(nodeDetail.startTime);
                        node.setEndTime(nodeDetail.endTime);
                        break;
                      }
                    }
                    for (JobExecData execData : execDataList) {
                      if (execData.getJobName().equals(jobInfo.getJobName())) {
                        if (execData.getParams() != null) {
                          node.setParams(execData.getParams().getParams());
                        }
                        if (execData.getOutParams() != null) {
                          node.setOutParams(execData.getOutParams().getParams());
                        }
                      }
                    }
                    return node;
                  }
                }).collect(Collectors.toList());

    nodes.sort(new Comparator<TaskExecutionGraph.TaskNode>() {
      @Override
      public int compare(TaskExecutionGraph.TaskNode t1, TaskExecutionGraph.TaskNode t2) {
        if (t1.getStartTime().before(t2.getStartTime())) {
          return -1;
        } else if (t1.getStartTime().after(t2.getStartTime())) {
          return 1;
        }
        return 0;
      }
    });

    taskGraph.setNodes(nodes);
    //failed or success,set end time as last updatetime.
    if (!"PREPARINGRUNNING".contains(executionDetail.getStatus())) {
      taskGraph.setEndTime(executionDetail.getUpdateTime());
    }
    return taskGraph;
  }

  /**
   * update task.
   *
   * @param taskGraph graph
   * @return status
   */
  @RequestMapping(value = "/admin/task/update", method = {RequestMethod.PUT, RequestMethod.POST})
  @ResponseBody
  public Object updateTask(@RequestBody TaskGraph taskGraph) throws IOException {
    UserInfo userInfo = ContextUtil.getCurrentUser();
    Resource resource = Resources
          .newResource(ResourceType.Job, taskGraph.getTaskId());
    this.operationAccessControl.checkPermission(resource, userInfo, JobType.Update);
    this.checkNonNullCronString(taskGraph.getScheduleCron());
    TaskInfo taskInfo = this.buildTaskInfo(taskGraph);
    taskInfo.setModifyTime(new Date());
    this.taskManager.updateTaskDetail(taskInfo);
    GroupInfo groupInfo = groupInfoService.getGroupInfo(taskInfo.getOwnerId());
    //AlertUtil.updateTaskAlertCategory(alertBaseUrl, groupInfo, taskInfo);
    return this.getResult(true);
  }


  /**
   * getTasksList.
   *
   * @param pageIndex pageIndex
   * @param pageSize pageSize
   * @param ownerId owner id (userid or groupid)
   * @param ownerTypeStr owner type(USER,GROUP,PUBLIC)
   * @return Object
   */
  @RequestMapping(value = {"/view/tasks"}, method = {RequestMethod.GET})
  public Object getTasksList(
        @RequestParam(value = "pageIndex", required = false, defaultValue = "1") int pageIndex,
        @RequestParam(value = "pageSize", required = false, defaultValue = "20") int pageSize,
        @RequestParam(value = "appId", required = false) String appId,
        @RequestParam(value = "keyWord", required = false) String keyWord,
        @RequestParam(value = "ownerType", required = false) String ownerTypeStr,
        @RequestParam(value = "scheduled", required = false) String scheduled,
        //scheduled noscheduled
        @RequestParam(value = "ownerId", required = false) Long ownerId) {
    if (keyWord != null && keyWord.length() == 0) {
      keyWord = null;
    }
    int offset = pageIndex == 1 ? 0 : (pageIndex - 1) * pageSize;
    OwnerType ownerType = null;
    if (ownerTypeStr != null && ownerTypeStr.length() > 0) {
      ownerType = OwnerType.valueOf(ownerTypeStr);
    }
    List<TaskInfo> taskInfos = null;
    UserInfo userInfo = ContextUtil.getCurrentUser();
    if (OwnerType.USER.equals(ownerType)) {
      taskInfos = taskManager
            .getUserTasks(userInfo.getUserId(), appId, keyWord, offset, pageSize + 1,
                  scheduled);
    } else if (OwnerType.GROUP.equals(ownerType)) {
      if (ownerId == null) {
        GroupInfo groupInfo = groupInfoService.getGroupInfo("All");
        ownerId = groupInfo.getGroupId();
      }
      taskInfos = taskManager.getGroupTasks(userInfo.getUserId(),
            ownerId, appId, keyWord, offset, pageSize + 1, scheduled);
    } else {
      taskInfos = taskManager.getUserAuthorizedTasks(userInfo.getUserId(), appId,
            keyWord, offset, pageSize + 1, scheduled);
    }

    PageInfo pageInfo = new PageInfo();
    pageInfo.setHasMore(taskInfos.size() > pageSize);
    if (taskInfos.size() > pageSize) {
      taskInfos = taskInfos.subList(0, pageSize);
    }
    if (taskInfos.size() > 0) {
      pageInfo.setPageIndex(pageIndex);
      pageInfo.setStart((pageIndex - 1) * pageSize + 1);
      pageInfo.setStop((pageIndex - 1) * pageSize + taskInfos.size());
    } else {
      pageInfo.setPageIndex(pageIndex - 1);
    }

    return this.getPagedResult(TaskInfoVo.buildFromTaskInfo(taskInfos), pageInfo);
  }

  @RequestMapping(value = {"/view/task/detail"}, method = {RequestMethod.GET})
  @ResponseBody
  public Object getTaskDetail(@RequestParam("id") String id) {
    TaskInfo taskInfo = taskManager.getTaskDetailInfo(id);
    TaskGraph taskGraph = this.buildTaskGraph(taskInfo);

    return getResult(taskGraph);
  }

  @RequestMapping(value = {"/view/exec/detail"}, method = RequestMethod.GET)
  @ResponseBody
  public Object getExecDetail(@RequestParam("execId") int execid,
        @RequestParam("taskId") String taskid,
        @RequestParam(value = "flowName", required = false) String flowName) {
    //TaskExecutionDetail execDetail = this.taskManager.getTaskExecutionDetail(execid);
    TaskInfo taskInfo = this.taskManager.getTaskDetailInfo(taskid);
    TaskExecutionGraph taskGraph = this.buildTaskExecutionGraph(execid, taskInfo);
    return getResult(taskGraph);
  }

  @RequestMapping(value = {"/admin/task"}, method = {RequestMethod.PUT})
  public Object changeTaskScheduleCron(@RequestParam("id") String id,
        @RequestParam("scheduleCron") String scheduleCron) {
    Resource resource = Resources.newResource(ResourceType.Job, id);
    this.operationAccessControl.checkPermission(resource,
          ContextUtil.getCurrentUser(), JobType.Update);
    TaskInfo taskInfo = taskManager.getTaskById(id);
    taskInfo.setModifyTime(new Date());
    taskInfo.setScheduleCron(scheduleCron);
    boolean scheduled = taskInfo.isScheduled();
    taskManager.unscheduleTask(id);
    taskManager.updateTask(taskInfo);
    if (scheduled) {
      taskManager.scheduleTask(id);
    }
    return getResult(true);
  }

  @RequestMapping(value = {"/admin/task/schedule/start"}, method = {RequestMethod.POST})
  public Object scheduleTaskStart(@RequestParam("taskId") String taskId) {
    Resource resource = Resources.newResource(ResourceType.Job, taskId);
    this.operationAccessControl
          .checkPermission(resource, ContextUtil.getCurrentUser(), JobType.Schedule);
    TaskInfo task = taskManager.scheduleTask(taskId);
    task.setModifyTime(new Date());
    return getResult(true);
  }

  @RequestMapping(value = {"/admin/task/schedule/stop"}, method = {RequestMethod.POST})
  public Object scheduleTaskStop(@RequestParam("taskId") String taskId) {
    Resource resource = Resources.newResource(ResourceType.Job, taskId);
    this.operationAccessControl
          .checkPermission(resource, ContextUtil.getCurrentUser(), JobType.Schedule);
    TaskInfo taskInfo = taskManager.unscheduleTask(taskId);
    taskInfo.setModifyTime(new Date());
    return getResult(true);
  }

  @RequestMapping(value = {"/admin/task/execute/start"}, method = {RequestMethod.POST})
  public Object executeTaskStart(@RequestParam("taskId") String taskId) {
    Resource resource = Resources.newResource(ResourceType.Job, taskId);
    this.operationAccessControl
          .checkPermission(resource, ContextUtil.getCurrentUser(), JobType.Schedule);
    taskManager.executeTask(taskId);
    TaskInfo taskInfo = this.taskManager.getTaskById(taskId);
    //sync execution flows immediately
    logger.info("sync task executions for this exec...");
    taskMonitorService.syncExecutions();
    return getResult(true);
  }

  @RequestMapping(value = {"/admin/task/execute/stop"}, method = {RequestMethod.POST})
  public Object executeTaskStop(@RequestParam("execId") int execId,
        @RequestParam("taskId") String taskId) {
    Resource resource = Resources.newResource(ResourceType.Job, taskId);
    this.operationAccessControl
          .checkPermission(resource, ContextUtil.getCurrentUser(), JobType.Schedule);
    taskManager.stopExecution(execId);
    TaskInfo taskInfo = taskManager.getTaskById(taskId);

    return getResult(true);
  }

  @RequestMapping(value = {"/admin/task"}, method = {RequestMethod.DELETE})
  public Object deleteTask(@RequestParam("id") String id) throws IOException {
    Resource resource = Resources.newResource(ResourceType.Job, id);
    this.operationAccessControl
          .checkPermission(resource, ContextUtil.getCurrentUser(), JobType.Remove);
    TaskInfo taskInfo = taskManager.getTaskById(id);
    if (taskInfo.isScheduled() || taskInfo.isDelProtection()) {
      throw new InvalidParamException("scheduled and protected task can not be deleted");
    }

    taskManager.deleteTaskById(id);
    GroupInfo groupInfo = groupInfoService.getGroupInfo(taskInfo.getOwnerId());
    //AlertUtil.deleteTaskAlertCategory(alertBaseUrl, groupInfo, taskInfo.getName());
    return getResult(true);
  }

  /**
   * getTaskExecutions.
   *
   * @param id id
   * @param pageIndex pageIndex
   * @param pageSize pageSize
   * @return Object
   */
  @RequestMapping(value = {"view/task/exec/history"}, method = {RequestMethod.GET})
  public Object getTaskExecutions(@RequestParam(value = "id", required = false) String id,
        @RequestParam(value = "pageIndex", required = false, defaultValue = "1") int pageIndex,
        @RequestParam(value = "pageSize", required = false, defaultValue = "20") int pageSize,
        @RequestParam(value = "status", required = false, defaultValue = "") String status,
        @RequestParam(value = "startTime", required = false)
        @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date startTime,
        @RequestParam(value = "endTime", required = false)
        @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date endTime) {
    if (id == null && id.trim().length() == 0) {
      id = "";
    } else {
      id = id.trim();
    }

    UserInfo userInfo = ContextUtil.getCurrentUser();
    List<UserGroup> userGroups = this.groupOperation.getUserGroups(userInfo.getUserId());
    List<Long> ids = new ArrayList<>();
    userGroups.forEach(userGroup -> {
      ids.add(userGroup.getGroupId());
    });
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    TaskExecResult taskExecResult = new TaskExecResult();
    try {
      taskExecResult = taskMonitorService
            .getTaskExecutions(id, userInfo.getUserId(), ids, pageIndex, pageSize,
                  status, startTime == null ? "" : sdf.format(startTime),
                  endTime == null ? "" : sdf.format(endTime));
    } catch (Exception e) {
      LogUtil.logStackTrace(logger, e);
    }

    Map<String, Object> resultMap = new HashMap<>();
    resultMap.put("pageIndex", pageIndex);
    resultMap.put("start", taskExecResult.getStart());
    resultMap.put("stop", taskExecResult.getStop());
    resultMap.put("hasMore", taskExecResult.isHasMore());
    resultMap.put("status", status);

    if (!taskExecResult.getSuccess()) {
      throw new InternalException(taskExecResult.getErrorMsg());

    }
    return getResult(taskExecResult.getData(), resultMap);
  }

  /**
   * get recent finish task.
   */
  @RequestMapping(value = "view/task/exec/recentfinish", method = RequestMethod.GET)
  @ResponseBody
  public Object getRecentFinishedExecs() {
    UserInfo userInfo = ContextUtil.getCurrentUser();
    List<UserGroup> userGroups = this.groupOperation.getUserGroups(userInfo.getUserId());
    List<Long> ids = new ArrayList<>();
    userGroups.forEach(userGroup -> {
      ids.add(userGroup.getGroupId());
    });
    TaskExecResult result =
          taskMonitorService.getRecentFinishedExecutions(userInfo.getUserId(), ids);
    Map<String, Object> resultMap = new HashMap<>();
    resultMap.put("pageIndex", 1);
    resultMap.put("start", 0);
    resultMap.put("stop", result.getStop());
    resultMap.put("hasMore", result.isHasMore());
    if (!result.getSuccess()) {
      throw new InternalException(result.getErrorMsg());
    }
    return getResult(result.getData(), resultMap);
  }

  /**
   * get task conunt .
   *
   * @param condition condition
   * @return count
   */
  @RequestMapping(value = "/view/task/exec/taskcount", method = RequestMethod.GET)
  @ResponseBody
  public Object getTaskCount(@RequestParam("condition") String condition) {
    UserInfo userInfo = ContextUtil.getCurrentUser();
    List<UserGroup> userGroups = this.groupOperation.getUserGroups(userInfo.getUserId());
    List<Long> ids = new ArrayList<>();
    userGroups.forEach(userGroup -> {
      ids.add(userGroup.getGroupId());
    });

    Integer scheduled = null;
    if ("ALL".equals(condition)) {
      scheduled = null;
    } else if ("SCHEDULED".equals(condition)) {
      scheduled = 1;
    } else if ("NOSCHEDULED".equals(condition)) {
      scheduled = 0;
    }

    Integer count = taskManager.getTaskCount(userInfo.getUserId(), ids, scheduled);
    Map<String, Object> resultMap = new HashMap<>();
    resultMap.put("count", count);
    return getResult(resultMap);
  }


  /**
   * 根据关键字查询任务名列表.
   *
   * @param keyWord keyword
   * @param appId appid
   * @return list of name/value pair
   */
  @RequestMapping(value = "/view/task/names", method = RequestMethod.GET)
  public Object searchTaskNameList(
        @RequestParam(value = "keyWord", defaultValue = "", required = false) String keyWord,
        @RequestParam(value = "appId", defaultValue = "", required = false) String appId) {
    UserInfo userInfo = ContextUtil.getCurrentUser();
    if (keyWord != null && keyWord.length() == 0) {
      keyWord = null;
    }
    if (appId != null && appId.length() == 0) {
      appId = null;
    }
    List<TaskInfo> taskInfos = taskManager.findTaskByApp(userInfo.getUserId(), appId, keyWord);
    List<TaskNamePair> namePairs = new ArrayList<>(taskInfos.size());
    taskInfos.forEach(new Consumer<TaskInfo>() {
      @Override
      public void accept(TaskInfo taskInfo) {
        TaskNamePair pair = new TaskNamePair();
        pair.setName(taskInfo.getName());
        pair.setValue(taskInfo.getTaskId());
        namePairs.add(pair);
      }
    });
    return this.getResult(namePairs);
  }

  /**
   * get task output params.
   *
   * @return task output params
   */
  @RequestMapping(value = "/view/task/out/params", method = RequestMethod.GET)
  public Object getTaskOutputParams(@RequestParam("taskId") String taskId) {
    List<JobInfo> jobInfos = this.taskManager.getJobsOfTask(taskId);
    Map<String, List<PackageOutParam>> params = new HashMap<>();
    for (JobInfo jobInfo : jobInfos) {
      PluginPackageV2 plugin =
            this.pluginManager.getPluginByNameVersion(ContextUtil.getCurrentUser().getUserId(),
                  jobInfo.getPkgName(), jobInfo.getPkgVersion());
      if (plugin.getOutParams() != null) {
        List<PackageOutParam> packageOutParams = plugin.getOutParams().getParamSchemas();
        if (packageOutParams != null && packageOutParams.size() > 0) {
          params.put(jobInfo.getJobName(), packageOutParams);
        }
      }
    }
    return getResult(params);
  }

  /**
   * get task output params by taskname.
   *
   * @return task output params
   */
  @RequestMapping(value = "/view/task/out/params/byname", method = RequestMethod.GET)
  public Object getTaskOutputParamsByName(@RequestParam("taskName") String taskName) {
    if (StringUtil.isEmpty(taskName)) {
      throw new InvalidParamException("taskName is null!");
    }
    List<JobInfo> jobInfos = this.taskManager.getJobsOfTaskByName(taskName);
    Map<String, List<PackageOutParam>> params = new HashMap<>();
    for (JobInfo jobInfo : jobInfos) {
      PluginPackageV2 plugin =
            this.pluginManager.getPluginByNameVersion(ContextUtil.getCurrentUser().getUserId(),
                  jobInfo.getPkgName(), jobInfo.getPkgVersion());
      if (plugin.getOutParams() != null) {
        List<PackageOutParam> packageOutParams = plugin.getOutParams().getParamSchemas();
        if (packageOutParams != null && packageOutParams.size() > 0) {
          params.put(jobInfo.getJobName(), packageOutParams);
        }
      }
    }
    return getResult(params);
  }


  @RequestMapping(value = "/admin/stop/exec", method = RequestMethod.POST)
  public Object stopExecution(@RequestParam("taskId") String taskId,
        @RequestParam("execId") int execId) {
    UserInfo userInfo = ContextUtil.getCurrentUser();
    TaskInfo taskInfo = this.taskManager.getTaskById(taskId);
    if (taskInfo.getOwnerType().equals(OwnerType.GROUP.name())) {
      GroupInfo groupInfo = this.groupInfoService.getGroupInfo(taskInfo.getOwnerId());
      if (!this.groupInfoService.isMasterOrOwner(userInfo.getUserId(), groupInfo.getGroupId())) {
        throw new AccessDeniedException("permission denied for exec stop", null);
      }
    } else if (taskInfo.getOwnerId() != userInfo.getUserId()) {
      throw new AccessDeniedException("permission denied for exec stop", null);
    }
    this.taskManager.stopExecution(execId);
    return getResult("success");
  }

  private void checkNonNullCronString(String cron) {
    if (StringUtil.isNotEmpty(cron)) {
      if (!CronExpression.isValidExpression(cron)) {
        throw new InvalidParamException("cron string is not valid");
      }
    }
  }
}
